/**
 * Basic logic for the internal representation of a graph.
 */
GraphApp = (function () {
  var Graph, Node, Vektor;

  /**
   *  Helps keeping a nodes position organized.
   *  @constructor
   *  @param {Integer} x The x-coordinate.
   *  @param {Integer} y The y-coordinate.
   */  
  Vektor = function (x, y) {
    this.x = x;
    this.y = y;
  };

  /**
   * Representation of a single graph node.
   * @constructor
   * @param {Integer|String} value The value of the node.
   * @param {Integer} id The identifier of the node.
   * @param {Integer} x The x-coordinate of the node.
   * @param {Integer} y The y-coordinate of the node.
   */
  Node = function (value, id, x, y) {
    // Public accessible properties.
    // The nodes internal identifier, unique on graph level.
    this.id = id;
    // The nodes x and y-coordinates.
    this.pos = new Vektor(x, y);
    // The value of the node.
    this.value = value;
    // Array of node ids representing the nodes children.
    this.children = [];
  };

  Node.prototype = {
    /**
     * Removes the edge from this node to the node specified by {id}.
     * @private
     * @param {Integer} id The identifier of the node to which the edge
     * should be removed. (In order to remove the edge from node 1 to 0 
     * this function is called on the node object of node 1 as follows: 
     * this._removeEdge(0).)
     */
    _removeEdge : function (id) {
      for (var i = this.children.length - 1; i >= 0; i--) {
        if (this.children[i] == id) {
          this.children.splice(i, 1);
        }
      }
    },

    /**
     * Updates the x and y coordinates of the node.
     * @private
     * @param {Integer} x The new x-coordinate of the node.
     * @param {Integer} y The new y-coordinate of the node.
     */
    _setCoordinates : function (x, y) {
      this.pos = new Vektor(x, y);
    },

    /**
     * Adds an edge from this node to the node with the id specified by {to}.
     * @private
     * @param {Integer} to The identifier of the node to which an edge should
     * be added. (In order to add an edge from node 0 to 1 this function
     * is called on the node object of node 0 as follows: this._addEdge(1).)
     */
    _addEdge : function (to) {
      for (var key in this.children) {
        if (this.children[key] === to) {
          return;
        }
      }
      this.children.push(to);
    }
  };

  /**
   * Representation of a complete graph
   * @constructor
   */
  Graph = function () {
    // The graphs nodes.
    this.nodes = new Array();
    // An autoincremented id to be able to assign each node an unique identifier.
    this._nodeId = 0;
  };

  Graph.prototype = {
    /**
     * Returns the next (within one graph) unique id
     * @private
     */
    _getNodeId : function () {
      this._nodeId = this._nodeId + 1;
      // ids might be in use after importing a graph
      // make sure to find an unused id
      while (this.nodes[this._nodeId] !== undefined) {
        this._nodeId = this._nodeId + 1;
      }
      return this._nodeId;
    },

    /**
     * Given a value and its coordinates add a new node to the graph
     * On adding a node the node is assigned with a unique id.
     * @param {Integer|String} value The value of the node to add.
     * @param {Integer} x The x-coordinate of the node to add.
     * @param {Integer} y The y-coordinate of the node to add.
     * @param {Integer} opt_id The identifier of the node to add. This is an
     * optional parameter. If opt_id is not defined on calling this function
     * the node to add gets a new currently unused id generated by the Graph 
     * object.
     * @returns {Node|Boolean} the node created by this function, false if the 
     * addition of the node into the graph failed.
     */
    addNode : function (value, x, y, opt_id) {
      var nid;
      if(!opt_id && opt_id != 0) {
        nid = this._getNodeId();
      } else {
        nid = opt_id;
      }
      var n = new Node(value, nid, x, y);
      if (typeof this.nodes[n.id] === "undefined") {
        this.nodes[n.id] = n;
        return n;
      } else {
        return false;
      }
    },

    /**
     * Removes the node specified by id from the graph. Also removes
     * all edges in the graph which point to the node.
     * @param {Integer} id The identifier of the node which should be removed
     * from the graph
     */
    removeNode : function (id) {
      if (typeof this.nodes[id] !== "undefined") {
        this._removeEdgesTo(id);
        delete this.nodes[id];
      } else {
        // throw error, node not in GraphApp.Graph
      }
    },

    /**
     * Removes all edges from the graph which point to the node specified
     * by id
     * @private
     * @param {Integer} id The identifier of the node from which all ingoing
     * edges should be deleted.
     */
    _removeEdgesTo : function (id) {
      for (var key in this.nodes) {
        this.nodes[key]._removeEdge(id);
      }
    },

    /**
     * Update coordinates for the node specified by id to x/y
     * @param {Integer} id The identifier of the node which coordinates should
     * be changed.
     * @param {Integer} x The new x-coordinate of the node.
     * @param {Integer} y The new y-coordinate of the node.
     */
    setCoordinates : function (id, x, y) {
      // validate input?
      if (typeof this.nodes[id] !== "undefined") {
        this.nodes[id]._setCoordinates(x, y);
      } else {
        // throw error, node not in GraphApp.Graph
      }
    },

    /**
     * Add an edge to the graph pointing from the node specified by
     * idfrom to the node with idto
     * @param {Integer} idfrom The identifier of the start node of the edge
     * which should be added to the graph.
     * @param {Integer} idto The identifier of the end node of the edge which
     * should be added to the graph.
     */
    addEdge : function (idfrom, idto) {
      if (this.nodes[idfrom] && this.nodes[idto]) {
        this.nodes[idfrom]._addEdge(idto);
      } else {
        // throw error, at least one id not in GraphApp.Graph
      }
    },

    /**
     * Removes the edge from the node with id idfrom pointing to the node
     * with id idto
     * @param {Integer} idfrom The identifier of the start node of the edge
     * which should be removed from the graph.
     * @param {Integer} idto The identifier of the end node of the edge which
     * should be removed from the graph.
     */
    removeEdge : function (idfrom, idto) {
      if (typeof this.nodes[idfrom] !== "undefined" && typeof this.nodes[idto] !== "undefined") {
        this.nodes[idfrom]._removeEdge(idto);
      } else {
        // throw error, at least one id not in GraphApp.Graph
      }
    },

    /**
     * Returns the array with the node ids to which the node with id points
     * to
     * @param {Integer} id The identifier ot the node from which the children
     * array should be returned.
     * @returns {Array|Boolean} The array containing the ids of the nodes 
     * children nodes, false if no node with the specified id exists in 
     * the graph.
     */
    neighbors : function (id) {
      if (typeof this.nodes[id] !== "undefined") {
        return this.nodes[id].children;
      } else {
        return false;
        // throw error, node not in GraphApp.Graph
      }
    },
    
    /**
     * Function to get a node by its id.
     * @param {id} id The identifier of the node which should be retrieved.
     * @returns {Node|Boolean} The node object with the specified id, false
     * if no node with this id is in the graph. 
     */  
    getNode : function (id) {
      if (typeof this.nodes[id] !== "undefined") {
        return this.nodes[id];
      } else {
        return false;
        // throw error, node not in GraphApp.Graph
      }
    },
    
    /**
     * Function to get the number of defined nodes in a graph. We can not
     * use this.nodes.length here because of the way we create and delete
     * nodes. Therefore this.nodes.length does not decrease when a node
     * is deleted from the graph.
     * @returns {Integer} The number of nodes in the graph.
     */
    getNumberOfNodes : function () {
      var ret = 0
      for (var key in this.nodes) {
        ret += 1;
      }
      return ret;
    },
    
    /**
     * Returns the maximum y-coordinate of any node in the graph.
     * @returns {Integer} The maximum y position of a node in the graph.
     */
    maxYPos : function () {
      var maxy = 0;
      for (var key in this.nodes) {
        if (this.nodes[key].pos.y > maxy) {
          maxy = this.nodes[key].pos.y;
        }
      }
      return maxy;
    }
  };

  return {
    Graph: Graph
  };
}());

/**
 * Function for the Import of a Graph object.
 * To use this function outside this class use the following notations:
 * Import: importedGraph = GraphApp.IO.read(filename);
 */
YUI().use('json', function (Y) {
  /**
   * Class for encapsulating the Import of Graph objects.
   */
  GraphApp.IO = (function () {
    var read;
    
    /**
     * Import a graph from a textfile containing a graph object in json 
     * notation.
     * @param {String} fname The filename of the file which contains the 
     * json text. In this version the filename is relative.
     * @returns {Graph} A Graph object with the properties specified by the
     * file at fname.
     */
    read = function (fname) {
      //opening file
      var req = new XMLHttpRequest();
      req.open('GET', fname, false);
      req.send(null);

      var jsonString = req.responseText;

      // JSON.parse throws a SyntaxError when passed invalid JSON
      try {
        var data = Y.JSON.parse(jsonString);
      }
      catch (e) {
        alert("Invalid data!");
      }
      var ret = new GraphApp.Graph()
      //var graph = data.graph;
      var graph = data.nodes;
      if(graph !== 'undefined'){
        // adding nodes
        for (var key in graph) {
          ret.addNode(
            graph[key].value,
            graph[key].x,
            graph[key].y,
            graph[key].id
          );
        }
        // adding children
        for (key in graph) {
          for (var childkey in graph[key].sons) {
            ret.addEdge(graph[key].id, graph[key].sons[childkey]);
          }
        }
      }
      return ret;
    }    
          
    return {
      
      read: read
    }
  }());
});  
  
  


